// Per-entity CRUD — URL `/admin/`. Everything here is DISCOVERED: // useCapabilityManifest → deriveEntityAdmins gives this entity's list query + // create/update/delete actions + the columns. We bind the shipped schema-driven // components to those tags: // - — live, reactive rows // - — fields from the input schema // // WRITES ARE GATED ON THE ACCESS THE API DECLARES, not on a scope string this // page invented. `spec.create.guards` is the procedure's own `guards:` / // `openAccess:`, straight off the capability map, so this gate and the server's // check read the same data and cannot drift. // // The decision is THREE-valued, and `unknown` is SHOWN, not hidden: a guard with // a `resource` extractor is answered per ROW by the server, and a browser // holding only global scopes cannot pre-compute it. Hiding those would empty the // admin for exactly the multi-tenant apps whose subjects carry no global scopes. // The server stays the authorization boundary and answers a typed ScopeError. // // Each row has a provenance ("why is this here?") drawer (08) + delete; the // layout's undo bar reverts the last change (00/09). Edit this file freely — // it's your template code, not a sealed runtime feature. import { useState, type ReactNode } from 'react' import type { PageMeta } from '@voltro/web' import { useParams, AutoForm, DataTable } from '@voltro/web' import { useCapabilityManifest, deriveEntityAdmins, useAccessDecision, requiredScopes, openAccessReason, useMutation, useProvenance, type EntityAction, type EntityAdminSpec, } from '@voltro/client' import { T, useTFn } from '@voltro/i18n' import { getCatalog } from '../../../locales/index' export const renderMode = 'ssr' as const export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({ title: getCatalog(locale)['meta.entity.title'], }) type Row = Record // "Why is this value here?" — a one-shot provenance lookup (innovation/08). // Its own component so useProvenance only runs while the drawer is open. const ProvenanceDrawer = ({ table, id, onClose }: { readonly table: string; readonly id: string; readonly onClose: () => void }): ReactNode => { const { data, loading, error } = useProvenance('app', table, id) return (

{c} }} /> {' '}

{loading ?

: null} {error ?

: null} {data ? (
{data.lastWrite?.at ?? '—'}
{data.lastWrite?.actor?.id ?? '—'}{data.lastWrite?.actor?.via ? : ''}
{data.attribution}
{data.derived ? <>
: null}
) : null}
) } // What the api requires for this action, in words — so an operator who hits a // ScopeError can read the scope they are missing instead of guessing at it. const AccessNote = ({ action }: { readonly action: EntityAction }): ReactNode => { const reason = openAccessReason(action.guards) const scopes = requiredScopes(action.guards) if (reason !== undefined) return if (scopes.length > 0) return return } // The bound CRUD surface for one entity. A child component so its hooks // (useAccessDecision/useMutation) get the real spec, never undefined. const EntityAdmin = ({ spec }: { readonly spec: EntityAdminSpec }): ReactNode => { const [sel, setSel] = useState(null) const t = useTFn() const create = useAccessDecision(spec.create.guards) const remove = useAccessDecision(spec.delete.guards) // Always-called; the tag is only used when spec.delete.tag exists (gated below). const del = useMutation>('app', spec.delete.tag ?? '__voltro.noop') // The column the api keys rows on — NOT a hard-coded `id`. A table keyed on // anything else would otherwise get an empty id sent on every delete. const pk = spec.pkColumn ?? 'id' const rowActions = (row: Row): ReactNode => { const id = String(row[pk] ?? '') return ( {spec.delete.tag && spec.editable && remove !== 'denied' && id ? ( ) : null} ) } return (

{spec.table}

{/* `.serverOnly()` columns are absent from `spec.columns` by construction — they never cross the wire, and the runtime REFUSES a mutation input that sets one. Saying so beats a silently shorter column list. */} {spec.serverOnlyColumns.length > 0 ? (

) : null} {spec.sensitiveColumns.length > 0 ? (

) : null} {!spec.editable ?

: null} {spec.create.tag && create !== 'denied' ? (

{create === 'unknown' ?

: null}
) : null}

{spec.list.tag ? ( ) : (

)} {sel ? setSel(null)} /> : null}
) } export default function EntityPage(): ReactNode { const { entity } = useParams<{ entity: string }>() const { manifest, loading } = useCapabilityManifest('app') const spec = manifest ? deriveEntityAdmins(manifest).find((e) => e.table === entity) : undefined if (loading && !manifest) { return
} if (!spec) { return (

{entity}

{c} }} />

) } return }